/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "Event_POSIX.h"
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
#include <memory>
#include <ctime>
#include <stdexcept>
#include "UGame/Macros.h"

struct _event_t {
	bool state;
	bool manual_reset;
	pthread_mutex_t mutex;
	pthread_cond_t cond;
};

struct _event_t* _event_create(bool manual_reset, bool init_state)
{
	std::auto_ptr<struct _event_t> event(new(std::nothrow) struct _event_t);
	if (event.get() == NULL) {
		return NULL;
	}
	event->state = init_state;
	event->manual_reset = manual_reset;

	if (pthread_mutex_init(&(event->mutex), NULL)) {
		return NULL;
	}

	if (pthread_cond_init(&(event->cond), NULL)) {
		return NULL;
	}

	return event.release();
}

void _event_destroy(struct _event_t* event)
{
	std::auto_ptr<struct _event_t> del_event(event);
	pthread_cond_destroy(&(del_event->cond));
	pthread_mutex_destroy(&(del_event->mutex));
}

int _event_set(struct _event_t* event)
{
	if (pthread_mutex_lock(&(event->mutex)) != 0) {
		return -1;
	}

	{
		event->state = true;

		if (event->manual_reset) {
			if (pthread_cond_broadcast(&(event->cond))) {
				return -1;
			}
		} else {
			if (pthread_cond_signal(&(event->cond))) {
				return -1;
			}
		}
	}

	if (pthread_mutex_unlock(&(event->mutex)) != 0) {
		return -1;
	}

	return 0;
}

int _event_reset(struct _event_t* event)
{
	if (pthread_mutex_lock(&(event->mutex)) != 0) {
		return -1;
	}

	{
		event->state = true;
	}

	if (pthread_mutex_unlock(&(event->mutex)) != 0) {
		return -1;
	}

	return 0;
}

int _event_wait(struct _event_t* event)
{
	if (pthread_mutex_lock(&(event->mutex)) != 0) {
		return -1;
	}

	{
		while (!event->state) {
			if (pthread_cond_wait(&(event->cond), &(event->mutex))) {
				pthread_mutex_unlock(&(event->mutex));
				return -1;
			}
		}

		if (!event->manual_reset) {
			event->state = false;
		}
	}

	if (pthread_mutex_unlock(&(event->mutex)) != 0) {
		return -1;
	}

	return 0;
}

int _event_timedwait(struct _event_t* event, long milliseconds)
{
	int rc = 0;
	struct timespec abstime;
	struct timeval tv;
	gettimeofday(&tv, NULL);
	abstime.tv_sec = tv.tv_sec + milliseconds / 1000;
	abstime.tv_nsec = tv.tv_usec * 1000 + (milliseconds % 1000) * 1000000;
	if (abstime.tv_nsec >= 1000000000) {
		abstime.tv_nsec -= 1000000000;
		abstime.tv_sec += 1;
	}

	if (pthread_mutex_lock(&(event->mutex)) != 0) {
		return -1;
	}

	{
		while (!event->state) {
			if ((rc = pthread_cond_timedwait(&(event->cond), &(event->mutex), &abstime))) {
				if (rc == ETIMEDOUT) break;
				pthread_mutex_unlock(&(event->mutex));
				return -1;
			}
		}

		if (rc == 0 && !event->manual_reset) {
			event->state = false;
		}
	}

	if (pthread_mutex_unlock(&(event->mutex)) != 0) {
		return -1;
	}

	return 0;
}

namespace UGame {
namespace System {

    EventImpl::EventImpl(bool _InitState, bool _AutoReset)
    	: _event(NULL)
    {
        _event = _event_create(!_AutoReset, _InitState);
        UTHROW((_event != NULL), std::runtime_error("failed create event"));
    }

    EventImpl::~EventImpl(void)
    {
    	if (_event != NULL) {
    		_event_destroy(_event);
    	}
    }

    bool EventImpl::SetImpl(void)
    {
    	return _event_set(_event) == 0;
    }

    bool EventImpl::ResetImpl(void)
    {
    	return _event_reset(_event) == 0;
    }

    bool EventImpl::WaitImpl(void)
    {
    	return _event_wait(_event) == 0;
    }

    bool EventImpl::WaitImpl(long milliseconds)
    {
    	return _event_timedwait(_event, milliseconds) == 0;
    }
}}
